#include <rtthread.h>
#include <rtdevice.h>
#include "uc_coding.h"
#include "uc_mask_ope.h"
#include "uc_seg_data_mem_manager.h"
#include "uc_log.h"

#define SLOT_DATA_LENGTH_LIMIT (UC_DATA_LENGTH_LIMIT - DATA_HEADER_LEN)
#define SLOT_BC_LENGTH_LIMIT (UC_BC_LENGTH_LIMIT - DATA_HEADER_LEN)
#define PACKET_LIST_LEN 8
#define CLEAR_PERIDOC (6000)
#define HASH_VALUE(val) ((val) & (PACKET_LIST_LEN - 1))

static t_data_packet data_packet[PACKET_LIST_LEN] = {0};

/**
 * @brief according to src_addr and packet_num returns a t_data_packet structure pointer
 *
 * @param  src_addr: recved data packet
 * @param  packet_num: package No
 * @return t_data_packet structure pointer
 *
 */
static t_data_packet *data_packet_find_node(unsigned int src_addr, unsigned char packet_num)
{
    t_data_packet *cur_packet = RT_NULL;

    rt_list_for_each_entry(cur_packet, &data_packet[HASH_VALUE(src_addr)].node, node)
    {
        if (cur_packet->src_addr == src_addr)
        {
            if (cur_packet->packet_num != packet_num)
            {
                TRACE_D("packet_num %d %d", cur_packet->packet_num, packet_num);
                data_seg_clear(cur_packet->data_seg, RT_FALSE);

                cur_packet->packet_num = packet_num;
                cur_packet->create_packet_tick = rt_tick_get();
            }
            TRACE_D("rr_data:src_addr 0x%x find exist data_packet 0x%x packet_num %d", cur_packet->src_addr, cur_packet, packet_num);
            return cur_packet;
        }
    }
    return RT_NULL;
}

/**
 * @brief according to cur_num returns a t_data_seg structure pointer
 *
 * @param  data_packet: t_data_packet structure pointer
 * @param  cur_num: current package No
 * @return t_data_seg structure pointer
 *
 */
static t_data_seg *data_seg_find_node(t_data_packet *data_packet, unsigned char cur_num)
{
    RT_ASSERT(data_packet != RT_NULL && data_packet->data_seg != RT_NULL);

    t_data_seg *cur_seg = RT_NULL;

    rt_slist_for_each_entry(cur_seg, &data_packet->data_seg->node, node)
    {
        if (cur_seg->cur_num == cur_num)
        {
            TRACE_D("rr_data:src_addr 0x%x find exist data_seg 0x%x", data_packet->src_addr, cur_seg);
            return cur_seg;
        }
    }
    return RT_NULL;
}

/**
 * @brief package management initialization
 *
 */
void data_packet_init(void)
{
    /* linked list initialization */
    rt_memset(data_packet, 0, sizeof(t_data_packet) * PACKET_LIST_LEN);
    for (int i = 0; i < PACKET_LIST_LEN; i++)
    {
        rt_list_init(&data_packet[i].node);
    }

    TRACE_D("rr_data:init suc");
}

/**
 * @brief package management de initialization
 *
 */
void data_packet_deinit(void)
{
    t_data_packet *temp_packet = RT_NULL;
    rt_list_t *next_node = RT_NULL;

    /* traverse the linked list to release all node data */
    for (int i = 0; i < PACKET_LIST_LEN; i++)
    {
        next_node = data_packet[i].node.next;

        while (next_node != &data_packet[i].node)
        {
            temp_packet = rt_list_entry(next_node, t_data_packet, node);

            data_packet_clear(temp_packet);

            next_node = next_node->next;
        }
        rt_list_init(&data_packet[i].node);
    }

    TRACE_D("rr_data:deinit suc");
}

/**
 * @brief  call this interface to release data_seg after data recved and merged
 *
 * @param  data_seg: data segment
 * @param  is_free_head: free head node flag
 *
 */
void data_seg_clear(t_data_seg *data_seg, rt_bool_t is_free_head)
{
    t_data_seg *temp_seg = RT_NULL;
    t_data_seg *seg = RT_NULL;
    t_data_seg *seg1 = data_seg;

    while (!rt_slist_isempty(&data_seg->node))
    {
        temp_seg = rt_slist_first_entry(&data_seg->node, t_data_seg, node);
        seg = temp_seg;

        rt_slist_remove(&data_seg->node, &temp_seg->node);
        rt_free(temp_seg->data);
        temp_seg->data = RT_NULL;
        rt_free(temp_seg);
        temp_seg = RT_NULL;
        TRACE_D("rr_data:clear data_seg 0x%x suc", seg);
    }

    if (is_free_head && data_seg != RT_NULL)
    {
        rt_free(data_seg);
        data_seg = RT_NULL;
        TRACE_I("rr_data:clear data_seg 0x%x suc", seg1);
    }
    else
    {
        rt_memset(data_seg, 0, sizeof(t_data_seg));
        rt_slist_init(&data_seg->node);
    }
}

/**
 * @brief  call this interface to release memory after data recved and merged
 *
 * @param  data_packet: recved data packet
 *
 */
void data_packet_clear(t_data_packet *data_packet)
{
    RT_ASSERT(data_packet != RT_NULL && data_packet->data_seg != RT_NULL);

    t_data_packet *temp_packet = data_packet;

    data_seg_clear(data_packet->data_seg, RT_TRUE);

    rt_free(data_packet);
    data_packet = RT_NULL;

    TRACE_I("rr_data:clear data_packet 0x%x suc", temp_packet);
}

/**
 * @brief scheduled clear to accept failed packets
 *
 */
void data_packet_period_clear(void)
{
    t_data_packet *temp_packet = RT_NULL;
    rt_list_t *next_node = RT_NULL;
    unsigned int cur_cost_tick = 0;
    rt_bool_t is_clear = RT_FALSE;

    /*
       traverse the link list node, when the current time minus the packet creation
       time is greater than the set cleaning cycle time, clean the packet
    */
    for (int i = 0; i < PACKET_LIST_LEN; i++)
    {
        next_node = data_packet[i].node.next;
        while (next_node != &data_packet[i].node)
        {
            temp_packet = rt_list_entry(next_node, t_data_packet, node);
            cur_cost_tick = rt_tick_get() - temp_packet->create_packet_tick;
            if (cur_cost_tick >= CLEAR_PERIDOC)
            {
                TRACE_I("rr_data_packet_period_clear temp_packet 0x%x", temp_packet);
                // data_seg_clear(temp_packet);
                is_clear = RT_TRUE;
            }
            next_node = next_node->next;
        }
        if (is_clear)
            rt_list_init(&data_packet[i].node);
    }
}

/**
 * @brief according to src_addr and packet_num returns a t_data_packet structure pointer
 *
 * @param  src_addr: recved data packet
 * @param  packet_num: package No
 * @return t_data_packet structure pointer
 *
 */
t_data_packet *data_packet_append(unsigned int src_addr, unsigned char packet_num)
{
    t_data_packet *new_packet = RT_NULL;

    /* according to src_addr and packet_num find node */
    t_data_packet *cur_packet = data_packet_find_node(src_addr, packet_num);

    /* if found, return to this node directly */
    if (cur_packet != RT_NULL)
    {
        return cur_packet;
    }
    else
    {
        /* if not found, malloc new node */
        new_packet = (t_data_packet *)rt_malloc(sizeof(t_data_packet));
        if (RT_NULL == new_packet)
        {
            TRACE_E("data_packet_append rt_malloc eror");
            return RT_NULL;
        }
        rt_memset(new_packet, 0, sizeof(t_data_packet));

        new_packet->src_addr = src_addr;
        new_packet->packet_num = packet_num;
        new_packet->create_packet_tick = rt_tick_get();

        new_packet->data_seg = (t_data_seg *)rt_malloc(sizeof(t_data_seg));
        if (RT_NULL == new_packet->data_seg)
        {
            TRACE_E("data_packet_append malloc err");
            return RT_NULL;
        }
        rt_memset(new_packet->data_seg, 0, sizeof(t_data_seg));
        rt_slist_init(&new_packet->data_seg->node);

        rt_list_insert_after(&data_packet[HASH_VALUE(src_addr)].node, &new_packet->node);
        TRACE_I("rr_data:src_addr 0x%x create new_packet 0x%x data_seg 0x%x packet_num %d", src_addr, new_packet, new_packet->data_seg, packet_num);
    }

    return new_packet;
}

/**
 * @brief record data and data length in segmented information

 * @param  data_packet: t_data_packet structure pointer, call function "rr_data_packet_append" to get
 * @param  data: segmented data address
 * @param  data_len: segmented data length
 * @param  cur_seg: current segmented data number
 * @param is_end: recv end flag
 * @return 0:suc, other:fail
 *
 */
int data_seg_append(t_data_packet *data_packet, void *data, unsigned int data_len, unsigned short cur_num, unsigned char is_end)
{
    t_data_seg *data_seg = RT_NULL;

    RT_ASSERT(data_packet != RT_NULL && data_packet->data_seg != RT_NULL);

    if ((0 == data_len) || (RT_NULL == data))
    {
        TRACE_E("rr_data:data seg append failed");
        return 1;
    }

    /* if there are duplicate packages, need to release the previous package first */
    data_seg = data_seg_find_node(data_packet, cur_num);
    if (data_seg != RT_NULL)
    {
        rt_free(data_seg->data);
        data_seg->data = RT_NULL;
    }
    else
    {
        /* append new node */
        data_seg = (t_data_seg *)rt_malloc(sizeof(t_data_seg));
        if (RT_NULL == data_seg)
        {
            TRACE_E("data_seg_append malloc err");
            return 2;
        }
        rt_memset(data_seg, 0, sizeof(t_data_seg));

        rt_slist_append(&data_packet->data_seg->node, &data_seg->node);
        TRACE_I("rr_data:src_addr 0x%x append data_seg 0x%x packet_num %d current_num %d data_len %d is_end %d",
                data_packet->src_addr, data_seg, data_packet->packet_num, cur_num, data_len, is_end);
    }

    data_seg->data = (unsigned char *)data;
    data_seg->data_len = data_len;
    data_seg->cur_num = cur_num;
    data_packet->is_end = is_end;
    data_packet->cur_num = cur_num;
    data_packet->create_packet_tick = rt_tick_get();

    return 0;
}

/**
 * @brief call this interface after recving all segmented data packets, and merge all segmented data packets into a complete data packet
 *
 * @param  data_packet: t_data_packet structure pointer, call function "rr_data_packet_append" to get
 * @param  data: address of the completeed data package after merging
 * @param  data_len: length of the completeed data package after merging
 * @return 0:suc, other:fail
 *
 */
int data_seg_merge(t_data_packet *data_packet, void **data, unsigned int *data_len)
{
    unsigned char *temp_data = RT_NULL;
    unsigned int total_len = 0;
    unsigned int offset = 0;
    t_data_seg *data_seg = RT_NULL;
    int cur_num = 0;
    int loss_packet_num = 0;
    int ret = 0;

    RT_ASSERT(data_packet != RT_NULL && data_packet->data_seg != RT_NULL);

    if (data_packet->is_end != SEGMENT_HAVE_END)
    {
        TRACE_E("data seg merge failed");
        data_seg_clear(data_packet->data_seg, RT_FALSE);
        return 1;
    }

    /* calculate the total length of segmented packets */
    rt_slist_for_each_entry(data_seg, &data_packet->data_seg->node, node)
    {
        /* packet continuity verification */
        if (data_seg->cur_num != cur_num)
        {
            loss_packet_num++;
        }
        cur_num++;
        total_len += data_seg->data_len;
    }

    /* merge all segmented packets */
    if (total_len > 0 && loss_packet_num == 0)
    {
        temp_data = (unsigned char *)rt_malloc(total_len);
        if (RT_NULL == temp_data)
        {
            TRACE_E("data_seg_merge malloc err");
            return 2;
        }
        rt_memset(temp_data, 0, total_len);

        rt_slist_for_each_entry(data_seg, &data_packet->data_seg->node, node)
        {
            rt_memcpy(temp_data + offset, data_seg->data, data_seg->data_len);
            offset += data_seg->data_len;
        }

        /* return the merged data address and data length */
        *data = temp_data;
        *data_len = total_len;

        TRACE_I("rr_data:src_addr 0x%x data 0x%x data_len %d merge data_seg suc", data_packet->src_addr, temp_data, total_len);
        ret = 0;
    }
    else
    {
        TRACE_I("rr_data:src_addr 0x%x data_len %d merge data_seg fail", data_packet->src_addr, total_len);
        ret = 1;
    }

    /* clear segmented data, after data merged */
    data_seg_clear(data_packet->data_seg, RT_FALSE);

    return ret;
}

/**
 * @brief
 *
 * @return 0:no segment, 1: continue differentical packets,
        2: end differentical packets; 3: fail.
 *
 */
int packege_segment_parsing(void *header,
                            void *in_data,
                            unsigned int in_data_len,
                            void **out_data,
                            unsigned int *data_len,
                            void **page)
{
    app_ps_header_t *ps_header = header;
    if (ps_header->property.segment_flag)
    {
        t_data_packet *data_packet = data_packet_append(ps_header->addr.src_addr, ps_header->packet_num);

        if (0 != data_seg_append(data_packet, in_data, in_data_len, ps_header->segment_info.current_num, ps_header->segment_info.is_end))
            return 3;

        if (data_packet->is_end != SEGMENT_HAVE_END)
        { // continue differentical packets
            return 1;
        }
        else
        {
            if (0 != data_seg_merge(data_packet, out_data, data_len))
                return 3;
            else
            {
                page = (void **)&data_packet;
                return 2;
            }
        }
    }

    return 0;
}
